package com.gylang.print.pdf.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.gylang.print.pdf.domain.PdfPageWrapper;
import com.gylang.print.pdf.domain.component.img.ImgComponent;
import com.gylang.print.pdf.domain.component.text.TextPdfComponent;
import com.gylang.print.pdf.enums.AlignEnums;
import org.apache.pdfbox.pdfwriter.COSWriter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author gylang
 * data 2021/6/4
 */
public class PdfUtil {

	private static final Map<String, File> FONT_POOL = new HashMap<>();

	static {
		// 加载字体库
		File[] files = FileUtil.file(PdfUtil.class.getResource("/font")).listFiles();

		if (null != files) {
			// 加载到字体池
			for (File file : files) {
				String name = file.getName();
				FONT_POOL.put(name, file);
			}
		}
	}

	/**
	 * 创建图片
	 *
	 * @param stream   pdf流
	 * @param document pdf
	 * @param imgUrl   图片地址
	 * @param x        x
	 * @param y        y
	 * @param w        宽
	 * @param h        长
	 */
	public static void writeImgByUrl(PDPageContentStream stream, PDDocument document, String imgUrl, float x, float y, float w, float h) {
		if (StrUtil.isNotEmpty(imgUrl)) {
			try {
				PDImageXObject imageObject = PDImageXObject.createFromByteArray(document, IoUtil.readBytes(new URL(imgUrl).openStream(), true), "imgUrl");
				writeImg(stream, imageObject, x, y, w, h);
			} catch (IOException e) {
				throw new ValidateException("读取文件失败: " + imgUrl);
			}
		}
	}

	/**
	 * 创建图片
	 *
	 * @param stream pdf流
	 * @param img    图片地址
	 * @param x      x
	 * @param y      y
	 * @param w      宽
	 * @param h      长
	 */
	public static void writeImg(PDPageContentStream stream, PDImageXObject img, float x, float y, float w, float h) {
		if (null != img) {
			try {
				stream.drawImage(img, x, y, w, h);
				stream.restoreGraphicsState();
			} catch (IOException e) {
				throw new ValidateException("读取文件失败: " + img);
			}
		}
	}

	/**
	 * 写入文本
	 *
	 * @param stream pdf流
	 * @param text   文本
	 * @param x      x坐标
	 * @param y      y坐标
	 * @param font   字体
	 * @param size   大小
	 */
	public static void writeTextByFontName(PDDocument document, PDPageContentStream stream, String text, Color color, float interval,
										   float x, float y, String font, float size) {

		PDFont pdFont = loadFontByName(document, font);
		writeText(stream, text, color, interval, x, y, pdFont, size);
	}

	/**
	 * 加载字体
	 *
	 * @param document pdf
	 * @param font     字体名称
	 * @return 字体
	 */
	public static PDFont loadFontByName(PDDocument document, String font) {

		try {
			return PDType0Font.load(document, FONT_POOL.get(font));
		} catch (IOException e) {
			e.printStackTrace();
			throw new ValidateException("加载字体失败: " + font + ", " + e.getMessage());
		}
	}

	/**
	 * 写入文本
	 *
	 * @param stream   写入pdf流
	 * @param text     文本
	 * @param color
	 * @param interval
	 * @param x        x坐标
	 * @param y        y坐标
	 * @param font     字体
	 * @param size     大小
	 */
	public static void writeText(PDPageContentStream stream, String text, Color color, float interval, float x, float y, PDFont font, float size) {

		try {
			// 开启文本输入
			stream.setNonStrokingColor(color);
			stream.beginText();
			// 设置文本定位
			stream.setFont(font, size);
			stream.newLineAtOffset(x, y);
			if (0F != interval) {
				stream.setCharacterSpacing(interval);

			}
			// 文本输入
			stream.showText(text);
			stream.endText();
		} catch (IOException e) {
			throw new ValidateException(e);
		}
	}


	/**
	 * 绘制矩阵
	 *
	 * @param stream
	 * @param color
	 * @param x
	 * @param y
	 * @param width
	 * @param height
	 */
	public static void writeRectangle(PDPageContentStream stream, Color color, float x, float y, float width, float height) {


		try {
			stream.setNonStrokingColor(color);
			stream.addRect(x, y, width, height);
			stream.fill();
		} catch (IOException e) {
			throw new ValidateException(e);
		}

	}

	/**
	 * 保存文档
	 *
	 * @param document     文档
	 * @param outputStream 输出流
	 */
	public static void save(PDDocument document, OutputStream outputStream) {
//		document.getDocumentInformation()
		try (COSWriter writer = new COSWriter(outputStream)) {
			writer.write(document);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 保存文档
	 *
	 * @param document 文档
	 * @param path     输出文件路径
	 */
	public static void save(PDDocument document, String path) {
//		document.getDocumentInformation()
		try {
			document.save(path);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 计算x轴起始左边
	 *
	 * @param component 文本信息
	 * @param content   内容
	 * @return x轴坐标
	 */
	public static float getTextX(TextPdfComponent component, String content) {

		PdfPageWrapper pageWrapper = component.getPageWrapper();

		return getX(pageWrapper.getWidth(), pageWrapper.getMarginLeftX(), pageWrapper.getMarginRightX(), component.getX(),
			getTextLength(component.getFont(), component.getSize(), content), component.getAlign());
	}

	/**
	 * 计算x轴起始左边
	 *
	 * @param x             x轴坐标
	 * @param boxWidth      容器宽度
	 * @param boxMarginLx   容器x边距
	 * @param boxMarginRx   容器x边距
	 * @param contentLength 内容长度
	 * @param align
	 * @return
	 */
	public static float getX(float boxWidth, float boxMarginLx, float boxMarginRx,
							 float x, float contentLength, AlignEnums align) {

		if (AlignEnums.ABSOLUTE.equals(align) || AlignEnums.VERTICAL_CENTER.equals(align)) {

			// x = left-margin + begin-X
			return x;
		} else if (AlignEnums.LEFT.equals(align)) {

			// x = box-left-margin +  left-margin  + begin-x
			return boxMarginLx + x;
		} else if (AlignEnums.CENTER.equals(align) || AlignEnums.HOR_CENTER.equals(align)) {
			// x =  box-width - (字符长) - box-left-margin text-margin-left - text-with
			return (boxWidth - boxMarginLx - boxMarginRx - contentLength) / 2 + boxMarginLx;
		} else {
			// x = box-left-margin +  left-margin  + begin-x
			return boxWidth - boxMarginRx - contentLength;
		}
	}

	/**
	 * 计算y轴起始左边
	 *
	 * @param boxHeight   容器高度
	 * @param boxMarginTy 容器x边距
	 * @param y           y轴坐标
	 * @param textHeight  文本高度
	 * @param align       位置类型
	 * @return y
	 */
	public static float getY(float boxHeight, float boxMarginTy, float boxMarginBy, float y, float textHeight, AlignEnums align) {

		if (AlignEnums.CENTER.equals(align) || AlignEnums.VERTICAL_CENTER.equals(align)) {
			return (boxHeight - textHeight - boxMarginTy - boxMarginBy) / 2 + boxMarginBy;
		} else {
			return y;
		}
	}

	/**
	 * 计算y轴起始坐标
	 *
	 * @param component 文本信息
	 * @param component 内容
	 * @return y轴坐标
	 */
	public static float getTextY(TextPdfComponent component) {
		if (AlignEnums.CENTER.equals(component.getAlign()) || AlignEnums.VERTICAL_CENTER.equals(component.getAlign())) {
			float textHeight = getTextHeight(component);
			return getTextTopY(component, textHeight);
		} else {

			return component.getY();
		}
	}

	public static float getTextHeight(TextPdfComponent component) {
		return (component.getSize() + component.getBrInterval()) * component.getMultiLine().size();
	}

	/**
	 * 计算y轴起始左边
	 *
	 * @param component  text信息
	 * @param textHeight
	 * @return y轴坐标
	 */
	private static float getTextTopY(TextPdfComponent component, float textHeight) {

		PdfPageWrapper pageWrapper = component.getPageWrapper();
		return getY(pageWrapper.getHeight(), pageWrapper.getMarginTopY(), pageWrapper.getMarginBottomY(), component.getY(), textHeight, component.getAlign());
	}


	/**
	 * 计算文本 右边浮动 x轴左边
	 *
	 * @param content   文本内容
	 * @param component text信息
	 * @return 右浮动 x坐标
	 */
	public static float getTextRightX(String content, TextPdfComponent component) {
		PdfPageWrapper pageWrapper = component.getPageWrapper();
		return pageWrapper.getWidth() - pageWrapper.getMarginRightX() - component.getMarginRightX() - getTextLength(component.getFont(), component.getSize(), content);
	}

	/**
	 * 计算文本 右边浮动 x轴左边
	 *
	 * @param content   文本内容
	 * @param component text信息
	 * @return 右浮动 x坐标
	 */
	public static float getTextCenterX(String content, TextPdfComponent component) {
		PdfPageWrapper pageWrapper = component.getPageWrapper();
		return (pageWrapper.getWidth() - pageWrapper.getMarginLeftX() - component.getMarginLeftX() - getTextLength(component.getFont(), component.getSize(), content)) / 2;
	}

	/**
	 * 计算文本 坐浮动 x轴左边
	 *
	 * @param component text信息
	 * @return 坐浮动 x坐标
	 */
	public static float getTextLeftX(TextPdfComponent component) {
		PdfPageWrapper pageWrapper = component.getPageWrapper();
		return pageWrapper.getMarginLeftX() + pageWrapper.getMarginLeftX() + component.getX();
	}

	/**
	 * 计算文本 绝对定位
	 *
	 * @param component text信息
	 * @return 绝对定位 x坐标
	 */
	public static float getTextAbsoluteX(TextPdfComponent component) {
		return component.getX();
	}

	/**
	 * 计算文本 长度
	 *
	 * @param content 文本内容
	 * @param font    字体
	 * @param size    大小
	 * @return 右浮动 x坐标
	 */
	public static float getTextLength(PDFont font, float size, String content) {
		try {
			return size * font.getStringWidth(content) / 1000;
		} catch (IOException e) {
			e.printStackTrace();
			throw new ValidateException("[pdf]计算坐标失败");
		}
	}


	public static List<String> splitLines(TextPdfComponent component) {

		String content = component.getContent();
		List<String> lines = null;
		// 分页处理
		// x =  box-width - (字符长) - box-left-margin - box-margin-left - text-with
		lines = splitLines(content, component.getWidth(), component.getFont(), component.getSize());
		return lines;
	}

	/**
	 * 拆分文本段落（换行）
	 *
	 * @param content   待输入文本
	 * @param lineWidth 行宽度
	 * @param font      字体
	 * @param fontSize  字体大小
	 * @return 返回文本列表
	 * @throws IOException IO异常
	 */
	public static List<String> splitLines(String content, float lineWidth, PDFont font, float fontSize) {
		List<String> lineList = null;
		try {
			// 如果待输入文本为空，或文本长度为0，则直接返回空列表
			if (content == null || content.trim().length() == 0) {
				// 返回空列表
				return new ArrayList<>(0);
			}
			// 先通过换行符切割
			String[] split = content.split("\\r?\\n");
			lineList = CollUtil.newArrayList();

			// 定义临时文本
			String tempText;
			// 计算文本真实宽度

			for (String text : split) {
				// 计算总行数（估计）
				float realWidth = 0;
				try {
					realWidth = fontSize * font.getStringWidth(text) / 1000;
				} catch (IllegalArgumentException e) {
					throw new IllegalArgumentException("[" + e.getMessage() + "], 异常文本: " + text);
				}
				int count = (int) (lineWidth / realWidth);
				// 计算的总行数与文本长度取最小值
				count = Math.min(count, text.length());
				// 定义开始索引
				int beginIndex = 0;
				// 遍历文本

				for (int i = count, len = text.length(); i <= len; i++) {
					// 截取临时文本
					tempText = text.substring(beginIndex, i);
					// 计算当前文本真实宽度
					realWidth = fontSize * font.getStringWidth(tempText) / 1000;
					// 如果真实宽度大于行宽度，则减少一个字符
					if (realWidth > lineWidth) {
						// 加入文本列表
						lineList.add(text.substring(beginIndex, i - 1));
						// 重置开始索引
						beginIndex = i - 1;
					}
					// 如果当前索引等于文本长度，则直接加入文本列表
					if (i == len) {
						// 加入文本列表
						lineList.add(text.substring(beginIndex, i));
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return lineList;

	}
	/**
	 * 拆分文本段落（换行）
	 *
	 * @param content   待输入文本
	 * @param lineWidth 行宽度
	 * @param font      字体
	 * @param fontSize  字体大小
	 * @return 返回文本列表
	 * @throws IOException IO异常
	 */
	public static List<String> splitLines(String content, float lineWidth, PDFont font, float fontSize, float firstWidth) {

		float width = firstWidth;
		List<String> lineList = null;
		try {
			// 如果待输入文本为空，或文本长度为0，则直接返回空列表
			if (content == null || content.trim().length() == 0) {
				// 返回空列表
				return new ArrayList<>(0);
			}
			// 先通过换行符切割
			String[] split = content.split("\\r?\\n");
			lineList = CollUtil.newArrayList();

			// 定义临时文本
			String tempText;
			// 计算文本真实宽度

			for (String text : split) {
				// 计算总行数（估计）
				float realWidth = 0;
				try {
					realWidth = fontSize * font.getStringWidth(text) / 1000;
				} catch (IllegalArgumentException e) {
					throw new IllegalArgumentException("[" + e.getMessage() + "], 异常文本: " + text);
				}
				int count = (int) (width / realWidth);
				// 计算的总行数与文本长度取最小值
				count = Math.min(count, text.length());
				// 定义开始索引
				int beginIndex = 0;
				// 遍历文本

				for (int i = count, len = text.length(); i <= len; i++) {
					// 截取临时文本
					tempText = text.substring(beginIndex, i);
					// 计算当前文本真实宽度
					realWidth = fontSize * font.getStringWidth(tempText) / 1000;
					// 如果真实宽度大于行宽度，则减少一个字符
					if (realWidth > width) {
						// 加入文本列表
						lineList.add(text.substring(beginIndex, i - 1));
						// 重置开始索引
						beginIndex = i - 1;
						width = lineWidth;
					}
					// 如果当前索引等于文本长度，则直接加入文本列表
					if (i == len) {
						// 加入文本列表
						lineList.add(text.substring(beginIndex, i));
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return lineList;

	}

	public static BufferedImage getImg(ImgComponent component) throws IOException {

		if (ImgComponent.ImgSource.URL.equals(component.getImgSource())) {
			return ImageIO.read(new URL(component.getImgUrl()));
		} else if (ImgComponent.ImgSource.FILE.equals(component.getImgSource())) {
			return ImageIO.read(component.getFile());
		} else if (ImgComponent.ImgSource.INPUT_STREAM.equals(component.getImgSource())) {
			return ImageIO.read(component.getInputStream());
		} else {
			return ImageIO.read(new ByteArrayInputStream(component.getBytes()));
		}
	}

	public static PDImageXObject getPDImageXObject(PDDocument document, BufferedImage image, String suffix) throws IOException {
		PDImageXObject pdImageXObject = null;
		try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
			ImageIO.write(image, suffix, outputStream);
			pdImageXObject = PDImageXObject.createFromByteArray(document, outputStream.toByteArray(), "pic" + System.currentTimeMillis() + "." + suffix);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return pdImageXObject;
	}


}
